import time
import json
import ssl
import socket
from typing import Dict
import paho.mqtt.client as mqtt
from config_loader import MqttConfig, PlcConfig
from utils.logger import get_logger
from utils.error_codes import ErrorCodeLoader
import os

# Create logger instance
logger = get_logger("mqtt_publisher.py+MqttPublisher")


class MqttPublisher:
    def __init__(self, mqtt_config: MqttConfig, plc_config: PlcConfig):
        self.config = mqtt_config
        self.plc_config = plc_config
        self.client = None
        self.plc_client = None  # Will be set by main.py
        self.connected = False
        self.logger = get_logger("mqtt_publisher.py+MqttPublisher")
        self.last_publish_interval = mqtt_config.publishing.publish_interval  # Track last publish interval
        self.mqtt_config_path = os.path.join(os.path.dirname(__file__), '..', 'config', 'mqtt_config.json')
        
        # Initialize error code loader
        error_codes_path = os.path.join(os.path.dirname(__file__), '..', 'config', 'error_codes.json')
        self.error_code_loader = ErrorCodeLoader(error_codes_path)
        
        # Initialize MQTT client
        try:
            client_id = f"equip_server_{self.plc_config.id}"
            self.client = mqtt.Client(client_id=client_id)
            self._setup_client()
        except Exception as e:
            logger.error(f"Failed to initialize MQTT client: {str(e)}")
            self.client = None

    def _setup_client(self):
        """Configure MQTT client"""
        # Set authentication information
        self.client.username_pw_set(
            self.config.broker.username,
            self.config.broker.password
        )
        
        # Set TLS
        if self.config.broker.tls_enabled:
            self.client.tls_set(cert_reqs=ssl.CERT_REQUIRED)
        
        # Set callback functions
        self.client.on_connect = self._on_connect
        self.client.on_disconnect = self._on_disconnect
        self.client.on_publish = self._on_publish

    def _on_connect(self, client, userdata, flags, rc):
        """Connection callback"""
        if rc == 0:
            self.connected = True
            logger.info("MQTT connected successfully")
            logger.info(f"MQTT client connected to broker {self.config.broker.host}:{self.config.broker.port}")
        else:
            self.connected = False
            error_messages = {
                1: "Incorrect protocol version",
                2: "Invalid client identifier",
                3: "Server unavailable",
                4: "Bad username or password",
                5: "Not authorized"
            }
            error_msg = error_messages.get(rc, f"Unknown error ({rc})")
            logger.error(f"MQTT connection failed: {error_msg} (error code: {rc}")
            logger.error(f"Failed to connect to broker {self.config.broker.host}:{self.config.broker.port}")

    def _on_disconnect(self, client, userdata, rc):
        """Disconnect callback"""
        self.connected = False
        if rc != 0:
            logger.error(f"MQTT unexpectedly disconnected, error code: {rc}")
            logger.error(f"Disconnected from broker {self.config.broker.host}:{self.config.broker.port}")
        else:
            logger.info("MQTT disconnected normally")
            
    def _on_publish(self, client, userdata, mid):
        """Publish callback"""
        logger.debug(f"Message published successfully, mid: {mid}")

    def connect(self) -> bool:
        """Connect to MQTT broker"""
        # Initialize client if not already done
        if self.client is None:
            client_id = f"equip_server_{self.plc_config.id}"
            self.client = mqtt.Client(client_id=client_id)
            self._setup_client()
        
        logger.info(f"Attempting to connect to MQTT broker at {self.config.broker.host}:{self.config.broker.port}")
        logger.info(f"Using client ID: {self.client._client_id}")
        logger.info(f"Using username: {self.config.broker.username}")
        
        # Check network connectivity first
        try:
            sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            sock.settimeout(5)
            result = sock.connect_ex((self.config.broker.host, self.config.broker.port))
            sock.close()
            
            if result != 0:
                logger.error(f"Cannot reach MQTT broker at {self.config.broker.host}:{self.config.broker.port}")
                return False
            else:
                logger.info(f"Network connectivity to broker OK: {self.config.broker.host}:{self.config.broker.port}")
        except Exception as e:
            logger.error(f"Network connectivity check failed: {str(e)}")
            return False
        
        for attempt in range(self.config.broker.retry_delay):
            try:
                if attempt > 0:
                    logger.warning(f"Retrying MQTT connection... (attempt {attempt + 1}/{self.config.broker.retry_delay})")
                    
                self.client.connect(
                    host=self.config.broker.host,
                    port=self.config.broker.port,
                    keepalive=self.config.broker.keepalive
                )
                self.client.loop_start()  # Start network loop
                time.sleep(1)  # Wait for connection to complete
                
                if self.connected:
                    logger.info("MQTT connection established successfully")
                    return True
                else:
                    logger.error("MQTT connection timed out")
                    
                # If not connected successfully, wait before retrying
                time.sleep(2)
            except ConnectionRefusedError as e:
                logger.error(f"MQTT connection refused: {str(e)}. Check if broker is running and accessible.")
                logger.error(f"Target broker: {self.config.broker.host}:{self.config.broker.port}")
                time.sleep(2)
            except OSError as e:
                logger.error(f"MQTT network error: {str(e)}. Check network connectivity.")
                logger.error(f"Target broker: {self.config.broker.host}:{self.config.broker.port}")
                time.sleep(2)
            except Exception as e:
                logger.error(f"MQTT connection exception: {str(e)}")
                logger.error(f"Target broker: {self.config.broker.host}:{self.config.broker.port}")
                time.sleep(2)
        logger.error("Failed to establish MQTT connection after all retry attempts")
        return False

    def disconnect(self):
        """Disconnect MQTT connection"""
        if self.client is None:
            logger.warning("MQTT client not initialized, nothing to disconnect")
            return
            
        if self.connected:
            logger.info(f"Disconnecting from MQTT broker {self.config.broker.host}:{self.config.broker.port}")
        self.client.loop_stop()
        self.client.disconnect()
        self.connected = False
        logger.info("MQTT client disconnected")

    def _process_data(self, data: Dict[str, any]) -> Dict[str, any]:
        """处理数据，转换键名为中文并处理故障码"""
        processed_data = {}
        
        # 创建寄存器名称映射（英文名到中文名）
        register_name_map = {}
        for reg in self.plc_config.registers:
            register_name_map[reg.name_en] = reg.name_cn
            
        # 处理每个数据项
        for key, value in data.items():
            # 获取中文键名
            chinese_key = register_name_map.get(key, key)
            
            # 特殊处理故障码
            if key == "faultcode":
                if isinstance(value, list):
                    # 如果是列表，格式化为指定格式
                    processed_data[chinese_key] = self.error_code_loader.format_fault_codes(value)
                elif isinstance(value, (int, float)):
                    # 如果是单个数值，转换为列表再处理
                    processed_data[chinese_key] = self.error_code_loader.format_fault_codes([int(value)])
                else:
                    processed_data[chinese_key] = str(value)
            else:
                processed_data[chinese_key] = value
                
        return processed_data

    def _update_mqtt_publish_interval(self, new_interval: int) -> bool:
        """
        Update MQTT publish interval in config file if changed
        :param new_interval: New publish interval in milliseconds
        :return: True if updated, False otherwise
        """
        # Validate the new interval - get min/max from parameter definition
        min_val, max_val = 500, 60000  # Default values
        
        # Try to get min/max from parameter definition
        if hasattr(self.plc_config, 'parameters') and self.plc_config.parameters:
            for param in self.plc_config.parameters:
                if param.name_en == "mqtt_publish_interval":
                    min_val = param.min
                    max_val = param.max
                    break
        
        if new_interval < min_val or new_interval > max_val:
            logger.warning(f"Invalid MQTT publish interval: {new_interval}ms. Must be between {min_val}ms and {max_val}ms")
            return False
            
        # Check if interval has changed
        if new_interval == self.last_publish_interval:
            return False
            
        logger.info(f"Updating MQTT publish interval from {self.last_publish_interval}ms to {new_interval}ms")
        
        try:
            # Read current config file
            config_path = os.path.join(os.path.dirname(__file__), '..', 'config', 'mqtt_config.json')
            with open(config_path, 'r', encoding='utf-8') as f:
                mqtt_config_data = json.load(f)
            
            # Update the publish interval
            mqtt_config_data['publishing']['publish_interval'] = new_interval
            
            # Write back to config file
            with open(config_path, 'w', encoding='utf-8') as f:
                json.dump(mqtt_config_data, f, ensure_ascii=False, indent=4)
            
            # Update in-memory config and last_publish_interval
            self.config.publishing.publish_interval = new_interval
            self.last_publish_interval = new_interval
            
            logger.info(f"MQTT publish interval successfully updated to {new_interval}ms")
            return True
            
        except Exception as e:
            logger.error(f"Failed to update MQTT publish interval in config file: {str(e)}")
            return False

    def _get_mqtt_publish_interval_from_plc_data(self, data: Dict[str, any]) -> int:
        """
        Extract MQTT publish interval from PLC data
        :param data: PLC data dictionary
        :return: MQTT publish interval in milliseconds, or None if not found
        """
        # Look for MQTT publish interval parameter in PLC data
        if hasattr(self.plc_config, 'parameters') and self.plc_config.parameters:
            for param in self.plc_config.parameters:
                if param.name_en == "mqtt_publish_interval" and param.name_en in data:
                    return data[param.name_en]
        return None

    def publish_data(self, data: Dict[str, any]) -> bool:
        """Publish data to MQTT"""
        logger.debug(f"Attempting to publish data, current connection status: {self.connected}")
        
        # Check if MQTT publish interval is present in data and update if needed
        new_interval = self._get_mqtt_publish_interval_from_plc_data(data)
        if new_interval is not None:
            self._update_mqtt_publish_interval(new_interval)
        
        if not self.connected:
            logger.info("MQTT client not connected, attempting to connect...")
            if not self.connect():
                logger.error("Failed to connect to MQTT broker, cannot publish data")
                return False
        
        # Build topic (format: modbus/data/{device ID})
        topic = f"{self.config.publishing.topic_prefix}"
        logger.info(f"Publishing data to topic: {topic}")
        
        try:
            # 处理数据，转换键名为中文并处理故障码
            processed_data = self._process_data(data)
            
            # Create payload in the required format
            payload = {
                "Services": [
                    {
                        "id": self.plc_config.id,
                        "Name": self.plc_config.name,
                        "properties": processed_data
                    }
                ]
            }
            
            # Log the data being published
            logger.info(f"Publishing data to {topic}")
            logger.debug(f"Published payload: {json.dumps(payload, ensure_ascii=False, indent=2)}")
            
            result = self.client.publish(
                topic=topic,
                payload=json.dumps(payload, ensure_ascii=False),
                qos=self.config.publishing.qos,
                retain=self.config.publishing.retain
            )
            
            logger.debug(f"Publish result: is_published={result.is_published()}, rc={result.rc}")
            result.wait_for_publish(timeout=10)  # Add timeout to prevent hanging
            
            if result.is_published():
                logger.info(f"Data published successfully to topic: {topic}")
                return True
            else:
                logger.error(f"Failed to publish data to topic: {topic}")
                logger.error(f"Publish result: is_published={result.is_published()}, rc={result.rc}")
                return False
        except Exception as e:
            logger.error(f"MQTT publish failed: {str(e)}")
            logger.error(f"Target topic: {topic}")
            self.connected = False
            return False
